package hawk.example.bookstore.repository.impl;

import static glz.hawkframework.support.ArgumentSupport.*;

import glz.hawkframework.dao.base.process.InsertProcessor;
import glz.hawkframework.dao.base.process.UpdateProcessor;
import glz.hawkframework.sql.condition.Condition;
import glz.hawkframework.sql.util.QueryWrapper;

import hawk.example.bookstore.mapper.PublisherMapper;
import hawk.example.bookstore.po.PublisherPo;
import hawk.example.bookstore.repository.PublisherRepository;
import hawk.example.bookstore.sql.provider.PublisherSqlProvider;
import hawk.example.bookstore.update.PublisherColumnUpdate;
import hawk.example.bookstore.update.PublisherUpdate;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import javax.annotation.Nullable;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class PublisherRepositoryImpl implements PublisherRepository {
    private final PublisherMapper publisherMapper;
    private final PublisherSqlProvider publisherSqlProvider;
    @Autowired(required = false)
    private InsertProcessor insertProcessor;
    @Autowired(required = false)
    private UpdateProcessor updateProcessor;

    public PublisherRepositoryImpl(PublisherMapper publisherMapper, PublisherSqlProvider publisherSqlProvider) {
        this.publisherMapper = argNotNull(publisherMapper, "publisherMapper");
        this.publisherSqlProvider = argNotNull(publisherSqlProvider, "publisherSqlProvider");
    }

    @Override
    public void insert(PublisherPo publisherPo) {
        argNotNull(publisherPo, "publisherPo");
        if (insertProcessor != null) {
            insertProcessor.beforeInsert(publisherPo);
        }
        if (publisherMapper.insert(publisherSqlProvider.insert(publisherPo)) != 1) {
            throw new IllegalStateException("Failed to insert publisherPo.");
        }
        if (insertProcessor != null) {
            insertProcessor.afterInsert(publisherPo);
        }
    }

    @Override
    public void insertSelective(PublisherPo publisherPo) {
        argNotNull(publisherPo, "publisherPo");
        if (insertProcessor != null) {
            insertProcessor.beforeInsert(publisherPo);
        }
        if (publisherMapper.insertSelective(publisherSqlProvider.insertSelective(publisherPo)) != 1) {
            throw new IllegalStateException("Failed to insert publisherPo.");
        }
        if (insertProcessor != null) {
            insertProcessor.afterInsert(publisherPo);
        }
    }

    @Override
    public void insertMultiple(List<PublisherPo> publisherPos) {
        final int defaultChunkSize = 200;
        insertMultiple(publisherPos, defaultChunkSize);
    }

    @Override
    public void insertMultiple(List<PublisherPo> publisherPos, int chunkSize) {
        argNotEmpty(publisherPos, "publisherPos");
        argument(chunkSize, c -> c > 0, c -> "The parameter['chunkSize'] must be greater than 0.");
        for (int i = 0; i < publisherPos.size(); i += chunkSize) {
            List<PublisherPo> chunk = publisherPos.subList(i, Math.min(i + chunkSize, publisherPos.size()));
            if (insertProcessor != null) {
                insertProcessor.beforeInsert(chunk);
            }
            if (publisherMapper.insertMultiple(publisherSqlProvider.insertMultiple(chunk)) != chunk.size()) {
                throw new IllegalStateException("Failed to insert publisherPos.");
            }
            if (insertProcessor != null) {
                insertProcessor.afterInsert(chunk);
            }
        }
    }

    @Override
    public void deleteByPrimaryKey(BigInteger publisherId, Long recordVersion) {
        argNotNull(publisherId, "publisherId");
        argNotNull(recordVersion, "recordVersion");
        if (publisherMapper.deleteGeneral(publisherSqlProvider.deleteByPrimaryKey(publisherId, recordVersion)) != 1) {
            throw new IllegalStateException("Failed to delete publisherPo.");
        }
    }

    @Override
    public void updateByPrimaryKey(PublisherUpdate publisherUpdate, BigInteger publisherId, Long recordVersion) {
        argNotNull(publisherUpdate, "publisherUpdate");
        argNotNull(publisherId, "publisherId");
        argNotNull(recordVersion, "recordVersion");
        HashMap<String, Object> params = null;
        if (updateProcessor != null) {
            params = new HashMap<>();
            params.put(UpdateProcessor.UPDATE_BY_FIELD_OBJECT, publisherUpdate);
            params.put(UpdateProcessor.RECORD_VERSION, recordVersion);
            params.put(UpdateProcessor.RECORD_ID, publisherId);
            updateProcessor.beforeUpdate(params);
        }
        if (publisherMapper.update(publisherSqlProvider.updateByPrimaryKey(publisherUpdate, publisherId, recordVersion)) != 1) {
            throw new IllegalStateException("Failed to update publisherPo.");
        }
        if (updateProcessor != null) {
            updateProcessor.afterUpdate(params);
        }
    }

    @Override
    public Optional<PublisherPo> getByPrimaryKey(BigInteger publisherId) {
        argNotNull(publisherId, "publisherId");
        return publisherMapper.selectOne(publisherSqlProvider.selectByPrimaryKey(publisherId));
    }

    /**
     * Throws an exception if found no required record by the primary key.
     */
    @Override
    public PublisherPo loadByPrimaryKey(BigInteger publisherId) {
        argNotNull(publisherId, "publisherId");
        return publisherMapper.selectOne(publisherSqlProvider.selectByPrimaryKey(publisherId)).orElseThrow(RuntimeException::new);
    }

    /**
     * Throws the supplied exception if found no required record by the primary key.
     */
    @Override
    public <E extends Throwable> PublisherPo loadByPrimaryKey(BigInteger publisherId, Supplier<? extends E> exceptionSupplier) throws E {
        argNotNull(publisherId, "publisherId");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        return publisherMapper.selectOne(publisherSqlProvider.selectByPrimaryKey(publisherId)).orElseThrow(exceptionSupplier);
    }

    @Override
    public boolean existByPrimaryKey(BigInteger publisherId) {
        argNotNull(publisherId, "publisherId");
        return publisherMapper.count(publisherSqlProvider.countByPrimaryKey(publisherId)) == 1;
    }

    /**
     * Throws an exception if the required record doesn't exist
     */
    @Override
    public void assertExistByPrimaryKey(BigInteger publisherId) {
        argNotNull(publisherId, "publisherId");
        if (publisherMapper.count(publisherSqlProvider.countByPrimaryKey(publisherId)) != 1) {
            throw new RuntimeException("Record not exist");
        }
    }

    /**
     * Throws the supplied exception if the required record doesn't exist
     */
    @Override
    public <E extends Throwable> void assertExistByPrimaryKey(BigInteger publisherId, Supplier<? extends E> exceptionSupplier) throws E {
        argNotNull(publisherId, "publisherId");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (publisherMapper.count(publisherSqlProvider.countByPrimaryKey(publisherId)) != 1) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public Optional<PublisherPo> queryOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return publisherMapper.selectOne(publisherSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    @Override
    public List<PublisherPo> queryMany(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return publisherMapper.selectMany(publisherSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    /**
     * Throws an exception if found no required record.
     */
    @Override
    public PublisherPo loadOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        return publisherMapper.selectOne(publisherSqlProvider.selectOrCountDynamic(queryWrapper)).orElseThrow(RuntimeException::new);
    }

    /**
     * Throws the supplied exception if found no required record.
     */
    @Override
    public <E extends Throwable> PublisherPo loadOne(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), q -> !q.isCount(), q -> "The count field in the queryWrapper should be set to false.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        return publisherMapper.selectOne(publisherSqlProvider.selectOrCountDynamic(queryWrapper)).orElseThrow(exceptionSupplier);
    }

    @Override
    public long count(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper));
    }

    @Override
    public boolean exist(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper)) > 0;
    }

    /**
     * Throws an exception if found no required record.
     */
    @Override
    public void assertExist(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        if (publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper)) <= 0) {
            throw new RuntimeException();
        }
    }

    /**
     * Throws the supplied exception if found no required record.
     */
    @Override
    public <E extends Throwable> void assertExist(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper)) <= 0) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public boolean existOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        return publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper)) == 1;
    }

    /**
     * Throws an exception if found no required record or found more than one record.
     */
    @Override
    public void assertExistOne(QueryWrapper queryWrapper) {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        if (publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper)) == 0) {
            throw new RuntimeException();
        }
        if (publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper)) > 1) {
            throw new RuntimeException();
        }
    }

    /**
     * Throws the supplied exception if found no required record or found more than one record.
     */
    @Override
    public <E extends Throwable> void assertExistOne(QueryWrapper queryWrapper, Supplier<? extends E> exceptionSupplier) throws E {
        argument(argNotNull(queryWrapper, "queryWrapper"), QueryWrapper::isCount, q -> "The count field in the queryWrapper should be set to true.");
        argNotNull(exceptionSupplier, "exceptionSupplier");
        if (publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper)) == 0) {
            throw exceptionSupplier.get();
        }
        if (publisherMapper.count(publisherSqlProvider.selectOrCountDynamic(queryWrapper)) > 1) {
            throw exceptionSupplier.get();
        }
    }

    @Override
    public long delete(@Nullable Condition condition) {
        return publisherMapper.deleteGeneral(publisherSqlProvider.deleteDynamic(condition));
    }

    @Override
    public long update(PublisherColumnUpdate publisherColumnUpdate, @Nullable Condition condition) {
        argNotNull(publisherColumnUpdate,"publisherColumnUpdate");
        HashMap<String, Object> params = null;
        if (updateProcessor != null) {
            params = new HashMap<>();
            params.put(UpdateProcessor.UPDATE_BY_COLUMN_OBJECT, publisherColumnUpdate);
            updateProcessor.beforeUpdate(params);
        }
        long affectedRowCount =  publisherMapper.updateGeneral(publisherSqlProvider.updateDynamic(publisherColumnUpdate, condition));
        if (updateProcessor != null) {
            updateProcessor.afterUpdate(params);
        }
        return affectedRowCount;
    }
}
